import sys
import datetime
import traceback
from flask import current_app as app

from app.errors import AddJobError, PackageLimit, CustomNameError
from app import celery_app
from app.db.models.batch_import import BatchImportAsset
from app.db.models.jobs import Job
from app.db.models.assets import Asset
from app.db.models.balances import Balances
from app.db.models.users import User
from app.db.models.alert_groups import AlertGroup
from app.libs.oms import Client
from app.libs.ip import is_ipv4, check_source_ip
from app.libs.regexp import NAME_RE
from app.libs.utils import get_valid_http_settings_by_job
from app.libs.utility import get_default_area, compared_value
from app.log.logs import CAssetLog
from app.validators.asset import TargetUrl
from app.handler.v2_jobs import JobVerifyHelper
from app.celery.sync_user_balance import sync_user_balances


def check_and_set_value(setting, balances, task_type):
    # check feq
    if setting.enable and (freq := setting.interval):
        if not (gte_value := getattr(balances.balance, f"{task_type}MaxFreq", None)) or \
                not compared_value(freq, gte_value.total, 'lt'):
            setting.interval = gte_value.total

    # check maxPage
    if max_page := getattr(setting.collect, 'maxPage', None):
        if not (gte_value := getattr(balances.balance, f"{task_type}MaxPage", None)) or \
                not compared_value(max_page, gte_value.total, 'gt'):
            setting.collect.maxPage = gte_value.total

    # check node
    if max_node := len(set(getattr(setting.collect, 'area', []) + getattr(setting.collect, 'area_ipv6', []))):
        if not (gte_value := getattr(balances.balance, f"{task_type}MaxNode", sys.maxsize)) or \
                not compared_value(max_node, gte_value.total, 'gt'):
            setting.collect.nodeCount = gte_value.total

    # check depth
    if max_depth := getattr(setting.collect, 'depth', None):
        if not (gte_value := getattr(balances.balance, f"{task_type}MaxDepth", sys.maxsize)) or \
                not compared_value(max_depth, gte_value.total, 'gt'):
            setting.collect.depth = gte_value.total

    return setting


@celery_app.task(acks_late=True)
def do_batch_import_asset(pk, **kwargs):
    """批量导入资产"""
    batch_import_asset = BatchImportAsset.objects.with_id(pk)
    uid = batch_import_asset.uid
    asset_type = batch_import_asset.assetType
    oms_client = Client()
    user = User.objects.get(pk=uid)

    alert_group_default = AlertGroup.objects.filter(
        uid=uid, isDefault=True).first()
    package_limit, _e = False, None
    success_count = 0

    for import_asset in batch_import_asset.assets:
        try:
            if package_limit:
                raise AddJobError(msg=_e.msg)
            if not alert_group_default:
                raise AddJobError(msg='未设置告警组')

            if not NAME_RE.fullmatch(import_asset.name):
                raise CustomNameError()

            # 解析ipv4
            sourceIp = import_asset.sourceIp
            if asset_type == 'host':
                if not sourceIp:
                    raise AddJobError(msg='资产地址不能为空')
                _is_ipv4, reason = is_ipv4(sourceIp)
                if not _is_ipv4:
                    raise AddJobError(msg=reason)
            else:
                if sourceIp:
                    status, msg = check_source_ip(sourceIp)
                    if not status:
                        raise AddJobError(msg=msg)

            # 解析参数target
            if not (target := import_asset.target):
                raise AddJobError(msg='资产地址不能为空')
            else:
                port = ''
                if asset_type == 'web':
                    try:
                        if (protocol := target.split(':')[0] + ':') not in ['https:', 'http:']:
                            raise AddJobError(msg='资产地址协议错误')
                    except Exception:
                        raise AddJobError(msg='资产地址协议错误')
                    try:
                        job_validate = TargetUrl(url=target)
                    except Exception as e:
                        if hasattr(e, 'msg'):
                            raise AddJobError(msg=e.msg)
                        raise e

                    hostname = job_validate.url.host
                    port = job_validate.url.port
                    ipType = 4
                    domain = None
                    if job_validate.url.host_type == 'domain':
                        domain = hostname
                        ipType = 0

                    host = f'{hostname}:{port}' if port else hostname
                    target_url = f'{protocol}//{host}'

                    if not port:
                        port = 443 if job_validate.url.scheme == 'https' else 80

                else:
                    protocol = 'http:'
                    hostname = sourceIp
                    ipType = 4
                    domain = None
                    host = sourceIp
                    target_url = sourceIp

            # 导入
            # assets
            asset = Asset()
            asset.taskSettings['target'] = target_url
            asset.alertSettings['alertGroupIds'] = [alert_group_default.id.__str__()]
            asset.uid = uid
            asset.target = target_url

            # job
            job = Job()
            area_dict = get_default_area(target_url, sourceIp)
            job.note = import_asset.name
            job.alertSettings.alertGroupIds.append(alert_group_default.id.__str__())
            job.uid = uid
            job.targetUrl = target_url
            job.assetType = asset_type
            job.target.protocol = protocol
            job.target.host = host
            job.target.hostname = hostname

            if port:
                job.target.port = port

            area = []
            area_ipv6 = []
            if asset_type == 'web':
                area = area_dict['area']
                area_ipv6 = area_dict['area_ipv6']

            job.target.ipType = ipType
            job.target.eTLD1 = domain
            job.sourceIp = import_asset.sourceIp
            job.httpSettings[0].target = target_url
            job.httpSettings[0].collect.area = area
            job.httpSettings[0].collect.area_ipv6 = area_ipv6
            job.httpSettings[0].sourceIp = import_asset.sourceIp
            job.pingSettings[0].target = target_url
            job.pingSettings[0].sourceIp = import_asset.sourceIp
            job.pingSettings[0].collect.area = area
            job.pingSettings[0].collect.area_ipv6 = area_ipv6
            job.import_type = batch_import_asset.type
            # 云防御导入, 默认认证成功
            if batch_import_asset.type == 'cloud':
                job.verification.isVerified = True
                job.cydEnabled = True
            else:
                try:
                    resp = oms_client.get('/api/inner/user_domain_valid/', params={
                        'username': user.username,
                        'domain': target_url})

                    if resp.get('msg', '') == 'success':
                        job.verification.isVerified = True
                        job.cydEnabled = True

                except Exception as e:
                    pass

            # http 资产默认关闭ssl监测
            if job.target.protocol == 'http:':
                job.sslSettings.enable = False

            if job.verification.isVerified is True:
                job.httpSettings = get_valid_http_settings_by_job(job)

            if asset_type == 'host':
                job.validate()
                job.save()
            else:
                asset.validate()
                job.validate()
                asset.save()
                job.assetId = asset.pk
                job.save()
                Asset.objects(pk=asset.pk).update_one(jobId=job.pk)

            balance = Balances.objects(uid=uid)

            if asset_type == "host":
                balance.update_one(inc__balance__host__used=1)
            else:
                balance.update_one(inc__balance__domain__used=1)
                balance = balance.first()

                # freq
                task_type_tuple = ('vul', 'ssl', 'securityEvent', 'asset', 'http', 'ping', 'content')
                for task_type in task_type_tuple:
                    if task_type in ('http', 'ping'):
                        for setting in getattr(job, f"{task_type}Settings"):
                            check_and_set_value(setting, balance, task_type)
                            job.save()
                        continue
                    elif task_type == 'asset':
                        asset = Asset.objects.filter(jobId=job.id).first()
                        new_job = asset
                        if not asset:
                            continue
                        setting = getattr(asset, f"taskSettings")
                    else:
                        setting = getattr(job, f"{task_type}Settings")
                        new_job = job
                    check_and_set_value(setting, balance, task_type)
                    new_job.save()

            import_asset.status, import_asset.detail = '成功', '导入成功'
            sync_user_balances(str(uid))
            success_count += 1

            if job.verification.isVerified and asset_type != 'host':
                JobVerifyHelper(job=job).enable_asset_task()

        except AddJobError as e:
            import_asset.status, import_asset.detail = '失败', e.msg
        except CustomNameError as e:
            import_asset.status, import_asset.detail = '失败', e.msg
        except PackageLimit as e:
            package_limit, _e = True, e
            import_asset.status, import_asset.detail = '失败', e.msg
        except Exception as e:
            traceback.print_exc()
            app.logger.exception(f'导入资产错误, {import_asset}')
            import_asset.status, import_asset.detail = '失败', '导入失败'
        finally:
            update_q = {
                'current_index': import_asset.index,
                f'assets__{import_asset.index}': import_asset,
                'last_update_time': datetime.datetime.utcnow()
            }
            BatchImportAsset.objects(pk=batch_import_asset.pk).update_one(**update_q)

    total = batch_import_asset.total
    fail = batch_import_asset.total - success_count
    if batch_import_asset.type == 'cloud':
        action_message = '批量导入云防御资产'
        detail = f'云防御批量添加{total}条资产，成功{success_count}条，失败{fail}条。'
    else:
        action_message = '本地批量添加资产'
        detail = f'本地批量导入{total}条资产，成功{success_count}条，失败{fail}条。'
    CAssetLog(**kwargs).info(action_message, detail)
